﻿using LinkGame.Models;
using LinkGame.Models.Variations;
using System.Media;
using System.Reflection;
using System.Text;

namespace LinkGame
{
    public partial class FrmMain : Form
    {
        #region Private Fields

        //private const string IconSet = @"Icons\circus.zip";
        private const int TileSize = 64;
        private const int DefaultPresetMapWidth = 8;
        private const int DefaultPresetMapHeight = 5;
        private const int DefaultPresetTileTypes = 10;

        private readonly BackgroundMusic _bgm = new(new[]
        {
            "Music\\2.mp3",
            "Music\\3.mp3",
            "Music\\1.mp3",
            "Music\\4.mp3",
            "Music\\5.mp3",
        })
        {
            Volume = .5f
        };

        private readonly FrmDiag _diagForm = new();
        private readonly int _formInitialHeight;
        private readonly int _formInitialWidth;

        private readonly List<GamePreset> _gamePresets =
        [
            new GamePreset("简单", DefaultPresetMapWidth, DefaultPresetMapHeight, DefaultPresetTileTypes, Keys.F2),
            new GamePreset("较难", 10, 6, 12, Keys.F3, 1.2f),
            new GamePreset("困难", 12, 8, 15, Keys.F4, 1.4f),
            new GamePreset("挑战", 15, 8, 20, Keys.F5, 1.6f),
            new GamePreset("地狱", 18, 10, 20, Keys.F6, 1.8f)
        ];

        private readonly List<GameLevel> _levels = [];
        private readonly Dictionary<Point, Panel> _panelMap = new();
        private readonly Setting _setting;
        private readonly SoundPlayer _soundPlayer = new(@"Music\click.wav");
        private int _currentLevelIdx;

        private (Point? Start, Point? End) _lastShownResolutionPoints;
        private Panel? _selectedPanel;

        private GamePreset? _selectedPreset;
        private int _selectedTileType = -1;
        private int _selectedX = -1;
        private int _selectedY = -1;

        #endregion Private Fields

        #region Public Constructors

        public FrmMain()
        {
            InitializeComponent();
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.UpdateStyles();
            _diagForm.ResolutionPathSelected += _diagForm_ResolutionPathSelected;
            _formInitialWidth = Width;
            _formInitialHeight = Height;
            _setting = LoadSetting();
        }

        #endregion Public Constructors

        #region Private Properties

        private GameLevel CurrentLevel => _levels[_currentLevelIdx];

        #endregion Private Properties

        #region Protected Methods

        protected override void OnClosed(EventArgs e)
        {
            pnlMain.Controls.Clear();
            foreach (ToolStripMenuItem item in mnuNewGame.DropDownItems)
            {
                item.Click -= Tmnu_Click;
            }

            foreach (var level in _levels)
            {
                level.MapTileChanged -= Level_MapTileChanged;
                level.PathResolved -= Level_PathResolved;
                level.LevelResolved -= Level_LevelResolved;
                level.Dispose();
            }

            _bgm.Stop();
            _bgm.Dispose();
            _soundPlayer.Dispose();

            _diagForm.ResolutionPathSelected -= _diagForm_ResolutionPathSelected;

            base.OnClosed(e);
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            tlblLevelName.Visible = false;
            tlblSelectedLevel.Visible = false;
            tProgress.Visible = false;
            tlblRemainingTime.Visible = false;
            mnuNewGame.DropDownItems.Clear();
            foreach (var preset in _gamePresets)
            {
                var tmnu = new ToolStripMenuItem(preset.Name)
                {
                    ShortcutKeys = preset.Shortcut,
                    Tag = preset,
                };
                tmnu.Click += Tmnu_Click;
                mnuNewGame.DropDownItems.Add(tmnu);
            }

            mnuStopGame.Enabled = false;
            mnuShowDiagWindow.Enabled = false;
        }

        #endregion Protected Methods

        #region Private Methods

        private void _diagForm_ResolutionPathSelected(object? sender, ResolutionPathSelectedEventArgs e)
        {
            if (_lastShownResolutionPoints is { Start: not null, End: not null })
            {
                var lastStartPnl = _panelMap[_lastShownResolutionPoints.Start.Value];
                var lastEndPnl = _panelMap[_lastShownResolutionPoints.End.Value];
                lastStartPnl.BackColor = Color.Transparent;
                lastEndPnl.BackColor = Color.Transparent;
            }

            if (e.Show)
            {
                var startPoint = e.SelectedPath?.Start;
                var endPoint = e.SelectedPath?.End;
                if (startPoint != null && endPoint != null)
                {
                    _lastShownResolutionPoints = (startPoint, endPoint);

                    var startPnl = _panelMap[startPoint.Value];
                    var endPnl = _panelMap[endPoint.Value];
                    startPnl.BackColor = Color.Yellow;
                    endPnl.BackColor = Color.Yellow;
                }
            }
        }

        private void ConfigureTilePanel(int x, int y, int tileType)
        {
            var pnl = _panelMap[new Point(x, y)];
            if (tileType != Map.FreeTileValue)
            {
                pnl.Name = $"tile_{x}_{y}";
                pnl.BackgroundImage = imageList.Images[$"{tileType}"];
                pnl.BackgroundImageLayout = ImageLayout.Center;
                pnl.Tag = $"Tile_{x}_{y}_{tileType}";
                pnl.Visible = true;
            }
            else
            {
                pnl.BackgroundImage = null;
                pnl.BackgroundImageLayout = ImageLayout.None;
                pnl.Tag = $"Tile_{x}_{y}_{-1}";
                pnl.Visible = false;
            }
        }

        private void DrawResolutionPath(ResolutionPath path)
        {
            using var g = pnlMain.CreateGraphics();
            foreach (var point in path)
            {
                var rect = new Rectangle(point.X * TileSize, point.Y * TileSize, TileSize, TileSize);
                g.FillRectangle(Brushes.LightBlue, rect);
            }
            Thread.Sleep(50);
            foreach (var point in path)
            {
                var rect = new Rectangle(point.X * TileSize, point.Y * TileSize, TileSize, TileSize);
                g.FillRectangle(SystemBrushes.Control, rect);
            }
        }

        private void InitializeGame(int width, int height, int totalTileTypes, string iconSet)
        {
            _selectedPanel = null;
            _selectedTileType = -1;
            _selectedX = -1;
            _selectedY = -1;

            // LoadIconSet($@"Icons\{iconSet}.zip");
            Utils.LoadIconSet(imageList, iconSet);

            // Initializes the game levels.
            if (_levels.Count > 0)
            {
                foreach (var level in _levels)
                {
                    level.MapTileChanged -= Level_MapTileChanged;
                    level.PathResolved -= Level_PathResolved;
                    level.LevelResolved -= Level_LevelResolved;
                }
            }

            _levels.Clear();
            _levels.Add(new("第一关：【常规】卡片无变幻", width, height, totalTileTypes, new NopVariation()));
            _levels.Add(new("第二关：【变种】所有卡片向左移动", width, height, totalTileTypes, new MovingLeftVariation()));
            _levels.Add(new("第三关：【变种】所有卡片向右移动", width, height, totalTileTypes, new MovingRightVariation()));
            _levels.Add(new("第四关：【变种】所有卡片向上移动", width, height, totalTileTypes, new MovingUpVariation()));
            _levels.Add(new("第五关：【变种】所有卡片向下移动", width, height, totalTileTypes, new MovingDownVariation()));
            _levels.Add(new("第六关：【变种】所有卡片由中心向两侧移动", width, height, totalTileTypes, new MovingCenterAsideVariation()));
            _levels.Add(new("第七关：【变种】所有卡片由中心向上下移动", width, height, totalTileTypes, new MovingCenterUpDownVariation()));

            foreach (var level in _levels)
            {
                level.MapTileChanged += Level_MapTileChanged;
                level.PathResolved += Level_PathResolved;
                level.LevelResolved += Level_LevelResolved;
            }

            // Reset the current level index.
            _currentLevelIdx = 0;

            // Initializes the tiles on the game pad.
            InitializeTiles(width, height);

            // Renders the map on the game pad.
            UpdateGamePad(width, height);

            // Update Level Name
            UpdateStatusLabel();

            // Play background music
            _bgm.Play();
        }

        private void InitializeTiles(int width, int height)
        {
            pnlMain.Visible = true;
            pnlMain.Width = width * TileSize;
            pnlMain.Height = height * TileSize;

            pnlMain.Left = 25;
            pnlMain.Top = 25;
            pnlMain.Controls.Clear();
            _panelMap.Clear();
            for (var x = 0; x < width; x++)
                for (var y = 0; y < height; y++)
                {
                    var pnl = new Panel
                    {
                        Top = y * TileSize,
                        Left = x * TileSize,
                        Width = TileSize,
                        Height = TileSize,
                    };
                    pnl.Click += pnl_Clicked;
                    _panelMap.Add(new Point(x, y), pnl);
                    pnlMain.Controls.Add(pnl);
                }

            Width = pnlMain.Width + 80;
            Height = pnlMain.Height + 150;
            pnlMain.Invalidate();
            //CenterToScreen();
        }

        private void Level_LevelResolved(object? sender, LevelResolvedEventArgs e)
        {
            if (_currentLevelIdx == _levels.Count - 1)
            {
                StopAndCloseGame();
                MessageBox.Show(@"恭喜！您已完成所有关卡！", @"信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                _currentLevelIdx++;
                UpdateStatusLabel();
            }
        }

        private void Level_MapTileChanged(object? sender, TileChangedEventArgs e)
        {
            ConfigureTilePanel(e.X, e.Y, e.NewTileType);
        }

        private void Level_PathResolved(object? sender, PathResolvedEventArgs e)
        {
            _soundPlayer.Play();
            DrawResolutionPath(e.Path);
            pnlMain.Refresh();
        }

        private Setting LoadSetting()
        {
            var setting = new Setting();
            if (!File.Exists("settings.txt")) return setting;
            using var fileStream = File.OpenRead("settings.txt");
            using var reader = new StreamReader(fileStream);
            var propertyInfoList =
                (from p in typeof(Setting).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where p.CanRead && p.CanWrite
                select p).ToList();
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
                if (line == null) continue;
                var kvp = line.Split('=');
                var key = kvp[0];
                var value = kvp[1];
                var propertyInfo = propertyInfoList.FirstOrDefault(p => p.Name == key);
                if (propertyInfo == null) continue;
                propertyInfo.SetValue(setting, value);
            }

            return setting;
        }

        private void mnuAbout_Click(object? sender, EventArgs e)
        {
            using var frm = new FrmAbout();
            frm.ShowDialog();
        }

        private void mnuExit_Click(object? sender, EventArgs e)
        {
            Close();
        }

        private void mnuSetting_Click(object? sender, EventArgs e)
        {
            using var frmSetting = new FrmSetting(_setting);
            if (frmSetting.ShowDialog() == DialogResult.OK)
            {
                _setting.IconSet = frmSetting.Setting?.IconSet;
                SaveSetting(_setting);
            }
        }

        private void mnuShowDiagWindow_Click(object? sender, EventArgs e)
        {
            _diagForm.UpdatePossiblePaths(CurrentLevel.PossibleResolutions);
            _diagForm.Show();
        }

        private void mnuStopGame_Click(object? sender, EventArgs e)
        {
            StopAndCloseGame();
        }

        private void pnl_Clicked(object? sender, EventArgs e)
        {
            if (sender is not Panel pnl) return;
            if (pnl.Tag is not string tag) return;

            var splittedTags = tag.Split('_');
            var clickedX = Convert.ToInt32(splittedTags[1]);
            var clickedY = Convert.ToInt32(splittedTags[2]);
            var clickedTileType = Convert.ToInt32(splittedTags[3]);
            if (clickedTileType == _selectedTileType)
            {
                if (clickedX != _selectedX || clickedY != _selectedY)
                {
                    var clickedPoint = new Point(clickedX, clickedY);
                    var selectedPoint = new Point(_selectedX, _selectedY);
                    if (CurrentLevel.HasValidConnection(clickedPoint, selectedPoint))
                    {
                        UpdateGamePad(_selectedPreset?.Width ?? DefaultPresetMapWidth, _selectedPreset?.Height ?? DefaultPresetMapHeight);
                        _diagForm.UpdatePossiblePaths(CurrentLevel.PossibleResolutions);
                    }
                }

                if (_selectedPanel is not null)
                {
                    _selectedPanel.BackColor = Color.Transparent;
                    _selectedPanel = null;
                    _selectedX = -1;
                    _selectedY = -1;
                    _selectedTileType = -1;
                    pnl.BackColor = Color.Transparent;
                }
            }
            else if (_selectedTileType == -1)
            {
                _selectedPanel = pnl;
                _selectedTileType = clickedTileType;
                _selectedX = clickedX;
                _selectedY = clickedY;
                pnl.BackColor = SystemColors.Highlight;
            }
            else
            {
                if (_selectedPanel is not null)
                {
                    _selectedPanel.BackColor = Color.Transparent;
                    _selectedPanel = null;
                    _selectedX = -1;
                    _selectedY = -1;
                    _selectedTileType = -1;
                    pnl.BackColor = Color.Transparent;
                }
            }
        }

        private void pnlMain_ControlRemoved(object sender, ControlEventArgs e)
        {
            if (e.Control is Panel pnl && (pnl.Tag?.ToString()?.StartsWith("Tile") ?? false))
            {
                pnl.Click -= pnl_Clicked;
                pnl.Dispose();
            }
        }

        private void SaveSetting(Setting setting)
        {
            var stringBuilder = new StringBuilder();
            var propertyInfoList =
                from p in typeof(Setting).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where p.CanRead && p.CanWrite
                select p;
            foreach (var propertyInfo in propertyInfoList)
            {
                var value = propertyInfo.GetValue(setting);
                stringBuilder.AppendLine($"{propertyInfo.Name}={value}");
            }

            File.WriteAllText("settings.txt", stringBuilder.ToString());
        }

        private void StopAndCloseGame()
        {
            _currentLevelIdx = 0;
            tlblLevelName.Text = string.Empty;
            tlblLevelName.Visible = false;
            tlblSelectedLevel.Text = string.Empty;
            tlblSelectedLevel.Visible = false;
            pnlMain.Controls.Clear();
            pnlMain.Visible = false;
            _bgm.Stop();
            mnuNewGame.Enabled = true;
            mnuStopGame.Enabled = false;
            mnuShowDiagWindow.Enabled = false;
            _diagForm.Hide();
            Width = _formInitialWidth;
            Height = _formInitialHeight;
            //CenterToScreen();
        }

        private void Tmnu_Click(object? sender, EventArgs e)
        {
            if (sender is ToolStripMenuItem tmnu)
            {
                var iconSet = "Icons\\circus.zip";
                if (!string.IsNullOrEmpty(_setting.IconSet))
                {
                    iconSet = _setting.IconSet;
                }

                _selectedPreset = (GamePreset?)tmnu.Tag;
                InitializeGame(_selectedPreset?.Width ?? DefaultPresetMapWidth, 
                    _selectedPreset?.Height ?? DefaultPresetMapHeight, 
                    _selectedPreset?.TileTypes ?? DefaultPresetTileTypes, 
                    iconSet);
                mnuStopGame.Enabled = true;
                mnuNewGame.Enabled = false;
                mnuShowDiagWindow.Enabled = true;
            }
        }

        private void UpdateGamePad(int width, int height)
        {
            for (var x = 0; x < width; x++)
                for (var y = 0; y < height; y++)
                {
                    ConfigureTilePanel(x, y, CurrentLevel.GetTileValue(x, y));
                }
        }

        private void UpdateStatusLabel()
        {
            tlblLevelName.Visible = true;
            tlblLevelName.Text = CurrentLevel.Name;

            tlblSelectedLevel.Visible = true;
            tlblSelectedLevel.Text = _selectedPreset?.Name;
        }

        #endregion Private Methods
    }
}